package com.zzyl.nursing.service.impl;

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.zzyl.common.constant.CacheConstants;
import com.zzyl.common.utils.DateUtils;
import com.zzyl.common.utils.StringUtils;
import com.zzyl.nursing.config.WebSocketServer;
import com.zzyl.nursing.domain.AlertData;
import com.zzyl.nursing.domain.DeviceData;
import com.zzyl.nursing.mapper.DeviceMapper;
import com.zzyl.nursing.service.IAlertDataService;
import com.zzyl.nursing.vo.AlertNotifyVo;
import com.zzyl.system.mapper.SysUserRoleMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import com.zzyl.nursing.mapper.AlertRuleMapper;
import com.zzyl.nursing.domain.AlertRule;
import com.zzyl.nursing.service.IAlertRuleService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.transaction.annotation.Transactional;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;

/**
 * 报警规则Service业务层处理
 *
 * @author ruoyi
 * @date 2024-12-17
 */
@Service
@Slf4j
@Transactional(rollbackFor = Exception.class)
public class AlertRuleServiceImpl extends ServiceImpl<AlertRuleMapper, AlertRule> implements IAlertRuleService {
    @Autowired
    private AlertRuleMapper alertRuleMapper;

    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private DeviceMapper deviceMapper;

    @Autowired
    private SysUserRoleMapper sysUserRoleMapper;
    //维修工
    @Value("${alert.deviceMaintainerRole}")
    private String deviceMaintainerRole;
    //管理员
    @Value("${alert.managerRole}")
    private String managerRole;
    @Autowired
    private IAlertDataService alertDataService;

    @Autowired
    private WebSocketServer webSocketServer;

    /**
     * 查询报警规则
     *
     * @param id 报警规则主键
     * @return 报警规则
     */
    @Override
    public AlertRule selectAlertRuleById(Long id) {
        return getById(id);
    }

    /**
     * 查询报警规则列表
     *
     * @param alertRule 报警规则
     * @return 报警规则
     */
    @Override
    public List<AlertRule> selectAlertRuleList(AlertRule alertRule) {
        return alertRuleMapper.selectAlertRuleList(alertRule);
    }

    /**
     * 新增报警规则
     *
     * @param alertRule 报警规则
     * @return 结果
     */
    @Override
    public int insertAlertRule(AlertRule alertRule) {
        return save(alertRule) ? 1 : 0;
    }

    /**
     * 修改报警规则
     *
     * @param alertRule 报警规则
     * @return 结果
     */
    @Override
    public int updateAlertRule(AlertRule alertRule) {
        return updateById(alertRule) ? 1 : 0;
    }

    /**
     * 批量删除报警规则
     *
     * @param ids 需要删除的报警规则主键
     * @return 结果
     */
    @Override
    public int deleteAlertRuleByIds(Long[] ids) {
        return removeByIds(Arrays.asList(ids)) ? 1 : 0;
    }

    /**
     * 删除报警规则信息
     *
     * @param id 报警规则主键
     * @return 结果
     */
    @Override
    public int deleteAlertRuleById(Long id) {
        return removeById(id) ? 1 : 0;
    }

    /**
     * 过滤规则
     */
    @Override
    public void alertFilter() {
        //查询所有有效的规则
        long count = count(Wrappers.<AlertRule>lambdaQuery().eq(AlertRule::getStatus, 1));
        if (count <= 0) {
            return;
        }
        //2.从redis中获取所有设备上的上报数据,如果数据为空，则结束
        List list = redisTemplate.opsForHash().values(CacheConstants.IOT_DEVICEDATA_LATEST);
        if (CollUtil.isEmpty(list)) {
            return;
        }
        //3.解析设备上报的每一条数据并存入到新的list集合中
        List<DeviceData> deviceDataList=new ArrayList<>();
        list.forEach(v -> deviceDataList.addAll(JSONUtil.toList(v.toString(), DeviceData.class)));
        //遍历设备数据并对条设备数据进行规则校验
        deviceDataList.forEach(deviceData -> alertRuleData(deviceData));
    }

    /**
     * 对设备信息进行规则校验
     *
     * @param deviceData
     */
    private void alertRuleData(DeviceData deviceData) {
        //因为规则中绑定了许多不同设备的物模型属性，所以要找到传入数据对应的设备，之后再找到对应的物模型属性
        //这里的查询条件是productKey和functionId，iotId为-1，即为该产品下的全部设备
        List<AlertRule> alertRules = list(Wrappers.<AlertRule>lambdaQuery()
                .eq(AlertRule::getProductKey, deviceData.getProductKey())
                .eq(AlertRule::getFunctionId, deviceData.getFunctionId())
                .eq(AlertRule::getIotId, "-1")
                .eq(AlertRule::getStatus, 1)
        );
        List<AlertRule> alertRules1 = list(Wrappers.<AlertRule>lambdaQuery()
                .eq(AlertRule::getProductKey, deviceData.getProductKey())
                .eq(AlertRule::getFunctionId, deviceData.getFunctionId())
                .eq(AlertRule::getIotId, deviceData.getIotId())
                .eq(AlertRule::getStatus, 1)
        );
        alertRules.addAll(alertRules1);
        //判断规则是否为空
        if (alertRules.size() == 0) {
            return;
        }
        //对物模型属性进行规则校验，判断是否需要报警并上传报警数据
        alertRules.forEach(alertRule -> {
            valueTest(deviceData, alertRule);
        });

    }

    private void valueTest(DeviceData deviceData, AlertRule alertRule) {
        String iotId = deviceData.getIotId();
        String alertEffectivePeriod = alertRule.getAlertEffectivePeriod();
        String aggCountKey = CacheConstants.IOT_ALERT_COUNT + iotId + ":" + deviceData.getFunctionId() + ":" + alertRule.getId();
        String clientSilentPeriodKey =CacheConstants.IOT_ALERT_SLIENT_PERIOD + iotId + ":" + deviceData.getFunctionId() + ":" + alertRule.getId();
        //判断时间是否在范围内
        String[] split = alertEffectivePeriod.split("~");
        LocalTime startTime = LocalTime.parse(split[0]);
        LocalTime endTime = LocalTime.parse(split[1]);
        //获取数据上报时间
        LocalTime time = deviceData.getAlarmTime().toLocalTime();
        if (time.isBefore(startTime) || time.isAfter(endTime)) {
        return;
        }
        //判断是否存在沉默周期
        String clientSilentPeriod =(String) redisTemplate.opsForValue().get(clientSilentPeriodKey);
        if (StringUtils.isNotEmpty(clientSilentPeriod)) {
            return;
        }
        Double dataValue = Double.valueOf(deviceData.getDataValue());
        Double value = Double.valueOf(alertRule.getValue());
        String operator = alertRule.getOperator();
        int compare = NumberUtil.compare(dataValue, value);
        if (operator.equals("<") && compare < 0 || operator.equals(">=") && (compare >= 0)) {
            //如果满足过滤条件继续向下执行
        }else {
            //删除redis报警数
            redisTemplate.delete(aggCountKey);
        }
        //上传报警数据,并增加报警次数缓存
        Integer aggCount = (Integer) redisTemplate.opsForValue().get(aggCountKey);
        //判断报警次数是否为空如果为空就初始化为1，不为空就加一
        int count = aggCount == null ? 1 : aggCount + 1;
        //判断是否达到持续周期，如果达到，就上传报警数据，未达到，就继续累加
        if (count == alertRule.getDuration()) {

            Integer alertDataType = alertRule.getAlertDataType();
            List<Long> userIds = null;
            if (alertDataType==0) {//老人异常数据
                if(deviceData.getLocationType().equals(0)){//随身设备
                    userIds = deviceMapper.selectNursingIdsByIotIdWithElder(iotId);
                }else if (deviceData.getLocationType().equals(1) && deviceData.getPhysicalLocationType()==2){
                    //床位设备异常数据
                    userIds = deviceMapper.selectNursingIdsByIotIdWithBed(iotId);
                }
            }else{
                //除床位之外设备异常
                userIds = sysUserRoleMapper.selectUserIdByRoleName(deviceMaintainerRole);
            }
            //获取管理员id
            List<Long> managerUserIds = sysUserRoleMapper.selectUserIdByRoleName(managerRole);
            //合并用户id
            Collection<Long> allUserIds = CollUtil.addAll(userIds, managerUserIds);
            //合并之后与可能有重复id,去重
            allUserIds = CollUtil.distinct(allUserIds);

            List<AlertData> alertDataList = addAlertData(deviceData, alertRule, allUserIds);
            // websocket推送消息
            websocketNotity(alertDataList.get(0), alertRule,allUserIds);
            //删除redis中报警次数缓存
            redisTemplate.delete(aggCountKey);
            //设置沉默周期到redis中
            redisTemplate.opsForValue().set(clientSilentPeriodKey, "设备" + iotId + "进入沉默周期", alertRule.getAlertSilentPeriod(), TimeUnit.MINUTES);
            log.info("设备" + iotId + "进入沉默周期");
        } else {
            redisTemplate.opsForValue().set(aggCountKey, count);
        }

    }
    /**
     * websocket推送消息
     * @param alertData
     * @param rule
     * @param allUserIds
     */
    private void websocketNotity(AlertData alertData, AlertRule rule, Collection<Long> allUserIds) {


        // 属性拷贝
        AlertNotifyVo alertNotifyVo = BeanUtil.toBean(alertData, AlertNotifyVo.class);
        alertNotifyVo.setFunctionName(rule.getFunctionName());
        alertNotifyVo.setAlertDataType(rule.getAlertDataType());
        alertNotifyVo.setNotifyType(1);
        // 向指定的人推送消息
        webSocketServer.sendMessageToConsumer(alertNotifyVo, allUserIds);

    }

    private List<AlertData> addAlertData(DeviceData deviceData, AlertRule alertRule,Collection<Long> allUserIds) {

        List<AlertData> list =new ArrayList();
        // 遍历allUserIds
        allUserIds.forEach(userId -> {
            // 对象拷贝
            AlertData alertData = BeanUtil.toBean(deviceData, AlertData.class);
            alertData.setAlertRuleId(alertRule.getId());
            // 心率<60,持续3个周期就报警
            String alertReason = CharSequenceUtil.format("{}{}{},持续{}个周期就报警", alertRule.getFunctionName(), alertRule.getOperator(), alertRule.getValue(), alertRule.getDuration());
            alertData.setAlertReason(alertReason);
            alertData.setStatus(0);
            alertData.setType(alertRule.getAlertDataType());
            alertData.setUserId(userId);
            alertData.setId(null);
            list.add(alertData);
        });

        alertDataService.saveBatch(list);
        return list;
    }
}
